from math import floor
from textwrap import wrap
from typing import List, Optional

try:
    from .jsobj import JavascriptObject
except:
    from jsobj import JavascriptObject  # type: ignore

__all__ = ['BoxDrawing']

BoxDrawing = JavascriptObject()

BoxDrawing.HORIZONTAL = '\u2500'                            # ─
BoxDrawing.HORIZONTAL_BOLD = '\u2501'                       # ━
BoxDrawing.VERTICAL = '\u2502'                              # │
BoxDrawing.VERTICAL_BOLD = '\u2503'                         # ┃
BoxDrawing.TOP_LEFT = '\u250c'                              # ┌
BoxDrawing.LEFT_TOP = BoxDrawing.TOP_LEFT
BoxDrawing.TOP_LEFT_BOLD = '\u250f'                         # ┏
BoxDrawing.LEFT_TOP_BOLD = BoxDrawing.TOP_LEFT_BOLD
BoxDrawing.TOP_RIGHT = '\u2510'                             # ┐
BoxDrawing.RIGHT_TOP = BoxDrawing.TOP_RIGHT
BoxDrawing.TOP_RIGHT_BOLD = '\u2513'                        # ┓
BoxDrawing.RIGHT_TOP_BOLD = BoxDrawing.TOP_RIGHT_BOLD
BoxDrawing.LOWER_LEFT = '\u2514'                            # └
BoxDrawing.LEFT_LOWER = BoxDrawing.LOWER_LEFT
BoxDrawing.LOWER_LEFT_BOLD = '\u2517'                       # ┗
BoxDrawing.LEFT_LOWER_BOLD = BoxDrawing.LOWER_LEFT_BOLD
BoxDrawing.LOWER_RIGHT = '\u2518'                           # ┘
BoxDrawing.RIGHT_LOWER = BoxDrawing.LOWER_RIGHT
BoxDrawing.LOWER_RIGHT_BOLD = '\u251b'                      # ┛
BoxDrawing.RIGHT_LOWER_BOLD = BoxDrawing.LOWER_RIGHT_BOLD


def getTable(table: List[List[str]], max_width: int, row_first: bool = True) -> str:
    """Generate a table using the Unicode box-drawing characters.

    Args:
        - `table` (`List[List[str]]`): The table of data to write in the box.
        - `max_width` (`int`): The maximum width the table.
        - `row_first` (`bool`, optional): Whether the row's index should be 
        used first in the `table` 2-D array. Defaults to `True`.

    Returns:
        - `str`: The generated table.
    """
    if not table:
        return ''
    if row_first:
        table = [[table[r][c]
                  for r in range(len(table))] for c in range(len(table[0]))]
    COLUMNS = len(table)
    ROWS = len(table[0])
    avgs = []
    maxes = []
    for c in range(COLUMNS):
        avgs.append(sum(map(len, table[c])) / len(table[c]))
        maxes.append(max(map(len, table[c])))
    s = sum(avgs)
    max_width -= COLUMNS + 1
    real = [min(floor(avgs[i] / s * max_width), maxes[i])
            for i in range(COLUMNS)]
    rest = max_width - sum(real)
    add_val = rest // COLUMNS
    for c in range(COLUMNS):
        if real[c] < maxes[c]:
            real[c] += add_val
    more = True
    while more:
        more = False
        for c in range(COLUMNS):
            if max_width <= sum(real):
                break
            if real[c] < maxes[c]:
                more = True
                real[c] += 1
    table2 = []
    table2.append(BoxDrawing.getChar(left=True, top=True))
    for c in range(COLUMNS):
        table2[0] += BoxDrawing.getChar(top=True) * real[c]
        table2[0] += BoxDrawing.getChar(top=True, midver=True)
    table2[0] = table2[0][:-1] + BoxDrawing.getChar(top=True, right=True)
    for r in range(ROWS):
        left = []
        for c in range(COLUMNS):
            w = wrap(table[c][r], real[c])
            left.append(w)
        while any(left):
            table2.append(BoxDrawing.getChar(left=True))
            for c in range(COLUMNS):
                try:
                    w = left[c].pop(0)
                except IndexError:
                    w = ''
                table2[-1] += w
                table2[-1] += ' ' * (max(0, real[c] - len(w)))
                table2[-1] += BoxDrawing.getChar(midver=True)
        table2.append(BoxDrawing.getChar(left=True, midhor=True))
        for c in range(COLUMNS):
            table2[-1] += BoxDrawing.getChar(midhor=True) * real[c]
            table2[-1] += BoxDrawing.getChar(midhor=True, midver=True)
        table2[-1] = table2[-1][:-1] + \
            BoxDrawing.getChar(right=True, midhor=True)
    table2[-1] = BoxDrawing.getChar(left=True, bottom=True)
    for c in range(COLUMNS):
        table2[-1] += BoxDrawing.getChar(bottom=True) * real[c]
        table2[-1] += BoxDrawing.getChar(bottom=True, midver=True)
    table2[-1] = table2[-1][:-1] + BoxDrawing.getChar(right=True, bottom=True)
    return '\n'.join(table2)


BoxDrawing.getTable = getTable


def getChar(
        left: bool = False,
        top: bool = False,
        right: bool = False,
        bottom: bool = False,
        midhor: bool = False,
        midver: bool = False,
        left_bold: bool = False,
        top_bold: bool = False,
        right_bold: bool = False,
        bottom_bold: bool = False,
        midhor_bold: bool = False,
        midver_bold: bool = False
) -> Optional[str]:
    """Get a box-drawing character based on the arguments.

    Args:
        `left` (`bool`, optional): Whether there is a border on the left side.
        `top` (`bool`, optional): Whether there is a border on the top side.
        `right` (`bool`, optional): Whether there is a border on the right 
        side.
        `bottom` (`bool`, optional): Whether there is a border on the bottom 
        side.
        `midhor` (`bool`, optional): Whether there is a horizontal border 
        between the top and bottom. Defaults to `False`.
        `midver` (`bool`, optional): Whether there is a vertical border 
        between the left and right. Defaults to `False`.
        `left_bold` (`bool`, optional): Whether the left border is bold.
        Defaults to `False`.
        `top_bold` (`bool`, optional): Whether the top border is bold.
        Defaults to `False`.
        `right_bold` (`bool`, optional): Whether the right border is bold.
        Defaults to `False`.
        `bottom_bold` (`bool`, optional): Whether the bottom border is bold.
        Defaults to `False`.
        `midhor_bold` (`bool`, optional): Whether the horizontal middle border 
        is bold. Defaults to `False`.
        `midver_bold` (`bool`, optional): Whether the vertical middle border 
        is bold. Defaults to `False`.

    Returns:
        `Optional[str]`: The character that match the descriptions, or `None` 
        if no character does.
    """
    if left:
        if top:
            if right:
                if bottom:
                    if left_bold or right_bold:
                        if top_bold or bottom_bold:
                            return '\u254b'
                        else:
                            return '\u2542'
                    else:
                        if top_bold or bottom_bold:
                            return '\u253f'
                        else:
                            return '\u253c'
                else:
                    return
            else:
                if bottom:
                    return
                else:
                    if left_bold:
                        if top_bold:
                            return '\u250f'
                        else:
                            return '\u250e'
                    else:
                        if top_bold:
                            return '\u250d'
                        else:
                            return '\u250c'
        else:
            if right:
                return
            else:
                if bottom:
                    if left_bold:
                        if bottom_bold:
                            return '\u2517'
                        else:
                            return '\u2516'
                    else:
                        if bottom_bold:
                            return '\u2515'
                        else:
                            return '\u2514'
                else:
                    if midhor:
                        if left_bold:
                            if midhor_bold:
                                return '\u2523'
                            else:
                                return '\u2520'
                        else:
                            if midhor_bold:
                                return '\u251d'
                            else:
                                return '\u251c'
                    if left_bold:
                        return '\u2503'
                    else:
                        return '\u2502'
    else:
        if top:
            if right:
                if bottom:
                    return
                else:
                    if right_bold:
                        if top_bold:
                            return '\u2513'
                        else:
                            return '\u2512'
                    else:
                        if top_bold:
                            return '\u2511'
                        else:
                            return '\u2510'
            else:
                if midver:
                    if top_bold:
                        if midver_bold:
                            return '\u2533'
                        else:
                            return '\u252f'
                    else:
                        if midver_bold:
                            return '\u2530'
                        else:
                            return '\u252c'
                else:
                    if top_bold:
                        return '\u2501'
                    else:
                        return '\u2500'
        else:
            if right:
                if bottom:
                    if right_bold:
                        if bottom_bold:
                            return '\u251b'
                        else:
                            return '\u251a'
                    else:
                        if bottom_bold:
                            return '\u2519'
                        else:
                            return '\u2518'
                else:
                    if midhor:
                        if right_bold:
                            if midhor_bold:
                                return '\u252b'
                            else:
                                return '\u2528'
                        else:
                            if midhor_bold:
                                return '\u2525'
                            else:
                                return '\u2524'
                    else:
                        if right_bold:
                            return '\u2503'
                        else:
                            return '\u2502'
            else:
                if bottom:
                    if midver:
                        if bottom_bold:
                            if midver_bold:
                                return '\u253b'
                            else:
                                return '\u2537'
                        else:
                            if midver_bold:
                                return '\u2538'
                            else:
                                return '\u2534'
                    else:
                        if midver:
                            if bottom_bold:
                                if midver_bold:
                                    return '\u253b'
                                else:
                                    return '\u2537'
                            else:
                                if midver_bold:
                                    return '\u2538'
                                else:
                                    return '\u2534'
                        if bottom_bold:
                            return '\u2501'
                        else:
                            return '\u2500'
                else:
                    if midver:
                        if midhor:
                            if midver_bold:
                                if midhor_bold:
                                    return '\u254b'
                                else:
                                    return '\u2542'
                            else:
                                if midhor_bold:
                                    return '\u253f'
                                else:
                                    return '\u253c'
                        else:
                            if midver_bold:
                                return '\u2503'
                            else:
                                return '\u2502'
                    elif midhor:
                        if midhor_bold:
                            return '\u2501'
                        else:
                            return '\u2500'
                    else:
                        return


BoxDrawing.getChar = getChar
